
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using System.IO;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2021 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco.Instance.Editor
{
    //批量链接场景中丢失引用的对象到预制体
    public class RelinkSceneObjectsToPrefab : EditorWindow
    {
        class PrefabInfo
        {
            public string assetPath;
            public GameObject prefab;
        }

        private Dictionary<string, PrefabInfo> _resourcesPrefab = new Dictionary<string, PrefabInfo>();

        [SerializeField]
        private List<string> _searchPaths = new List<string>();
        [SerializeField]
        private bool _isDestroyRelinkedTargets = true;

        private SerializedObject _serializedObject = null;
        private SerializedProperty _propertySearchPaths = null;

        private string _currentDefaultSearchKey = string.Empty;
        private bool _isHaveCurrentDefaultSearchKeyInList = false;

        [MenuItem("shaco/Tools/RelinkSceneObjectsToPrefab")]
        static void OpenWindow()
        {
            var window = (RelinkSceneObjectsToPrefab)EditorWindow.GetWindow(typeof(RelinkSceneObjectsToPrefab));
            window.Show();
        }

        void Init()
        {
            _serializedObject = new SerializedObject(this);
            _propertySearchPaths = _serializedObject.FindProperty("_searchPaths");
            RefillDefaultSearchKey();

            Selection.selectionChanged -= OnSelectionChanged;
            Selection.selectionChanged += OnSelectionChanged;

            Undo.undoRedoPerformed -= OnDoOrUndoChanged;
            Undo.undoRedoPerformed += OnDoOrUndoChanged;
        }

        void OnDestroy()
        {
            Undo.undoRedoPerformed -= OnDoOrUndoChanged;
            Selection.selectionChanged -= OnSelectionChanged;
        }

        void OnGUI()
        {
            if (null == _serializedObject)
                Init();

            Undo.RecordObject(this, "RelinkSceneObjectsToPrefab");

            if (1 == Selection.gameObjects.Length)
                GUILayout.Label(string.Format("Select '{0}' in scene", Selection.gameObjects[0].name));
            else
                GUILayout.Label("Select object in scene: " + Selection.gameObjects.Length);

            _isDestroyRelinkedTargets = EditorGUILayout.Toggle("Auto destroy re-linked targets", _isDestroyRelinkedTargets);

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(_propertySearchPaths, true);
            if (EditorGUI.EndChangeCheck())
            {
                _serializedObject.ApplyModifiedProperties();
                _isHaveCurrentDefaultSearchKeyInList = _searchPaths.Contains(_currentDefaultSearchKey);
            }

            EditorGUI.BeginDisabledGroup(_isHaveCurrentDefaultSearchKeyInList || null == Selection.activeGameObject);
            {
                if (GUILayout.Button("RefreshSearchPath"))
                {
                    RefillDefaultSearchKey();
                }
            }
            EditorGUI.EndDisabledGroup();

            bool noSelectObjectInScene = 0 == Selection.gameObjects.Length;
            bool noSearchPath = 0 == _searchPaths.Count;
            EditorGUI.BeginDisabledGroup(noSelectObjectInScene || noSearchPath);
            {
                if (GUILayout.Button("Relink" + (noSelectObjectInScene ? "(no select)" : "") + (noSearchPath ? " (no search path)" : "")))
                {
                    RelinkPrefabs(Selection.gameObjects);
                }
            }
            EditorGUI.EndDisabledGroup();
        }

        void OnSelectionChanged()
        {
            this.Repaint();
        }

        void OnDoOrUndoChanged()
        {
            _serializedObject = new SerializedObject(this);
            _propertySearchPaths = _serializedObject.FindProperty("_searchPaths");
            _isHaveCurrentDefaultSearchKeyInList = _searchPaths.Contains(_currentDefaultSearchKey);
            this.Repaint();
        }

        void RefillDefaultSearchKey()
        {
            if (null == Selection.activeGameObject)
                return;

            _isHaveCurrentDefaultSearchKeyInList = _searchPaths.Contains(_currentDefaultSearchKey);
            if (_isHaveCurrentDefaultSearchKeyInList)
                return;

            _currentDefaultSearchKey = "/" + Selection.activeGameObject.name;
            _isHaveCurrentDefaultSearchKeyInList = true;

            _propertySearchPaths.InsertArrayElementAtIndex(_propertySearchPaths.arraySize);
            _propertySearchPaths.GetArrayElementAtIndex(_propertySearchPaths.arraySize - 1).stringValue = _currentDefaultSearchKey;
            _serializedObject.ApplyModifiedProperties();
        }

        void RelinkPrefabs(GameObject[] objects)
        {
            if (null == objects || 0 == objects.Length)
            {
                Debug.LogError("RelinkSceneObjectsToPrefab RelinkPrefabs error: objects is empty");
                return;
            }

            _resourcesPrefab.Clear();
            var findAssetsPath = AssetDatabase.FindAssets("t:prefab")
                                        .Select(v => AssetDatabase.GUIDToAssetPath(v))
                                        .Where(v =>
                                        {
                                            foreach (var iter in _searchPaths)
                                            {
                                                if (v.Contains(iter))
                                                    return true;
                                            }
                                            return false;
                                        });
            // .Where(v => v.Contains("/LevelBuilding") || v.Contains("/Environment") || v.Contains("/SM_") || v.Contains("/SA_"));
            foreach (var iter in findAssetsPath)
            {
                var assetName = Path.GetFileNameWithoutExtension(iter);
                if (_resourcesPrefab.ContainsKey(assetName))
                {
                    Debug.LogError("duplicate path name=" + iter);
                    continue;
                }
                _resourcesPrefab.Add(assetName, new PrefabInfo() { assetPath = iter });
            }

            foreach (var iter in objects)
            {
                var sourceName = GetSourcePrefabName(iter);
                PrefabInfo prefabInfo = null;
                if (!_resourcesPrefab.TryGetValue(sourceName, out prefabInfo))
                {
                    Debug.LogError("RelinkSceneObjectsToPrefab RelinkPrefabs error: not found name=" + iter.name + " source name=" + sourceName);
                    continue;
                }

                RelinkPrefab(iter, prefabInfo);
            }

            var currentPrefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
            if (null != currentPrefabStage)
                EditorUtility.SetDirty(currentPrefabStage.prefabContentsRoot);
        }

        void RelinkPrefab(GameObject obj, PrefabInfo info)
        {
            if (IsValidPrefab(obj))
                return;

            if (null == info.prefab)
                info.prefab = AssetDatabase.LoadAssetAtPath<GameObject>(info.assetPath);

            var oldPos = obj.transform.position;
            var oldRotation = obj.transform.rotation;
            var oldScale = obj.transform.localScale;
            var oldParent = obj.transform.parent;
            var oldName = obj.name;
            var oldSiblingIndex = obj.transform.GetSiblingIndex();

            var missingNameIndex = oldName.LastIndexOf(" (Missing Prefab)");
            if (missingNameIndex >= 0)
                oldName = oldName.Remove(missingNameIndex);

            if (_isDestroyRelinkedTargets)
                MonoBehaviour.DestroyImmediate(obj.gameObject);
            var newObject = PrefabUtility.InstantiatePrefab(info.prefab) as GameObject;

            newObject.transform.SetParent(oldParent);
            newObject.transform.SetSiblingIndex(oldSiblingIndex);
            newObject.transform.position = oldPos;
            newObject.transform.rotation = oldRotation;
            newObject.transform.localScale = oldScale;
            newObject.name = oldName;
        }

        string GetSourcePrefabName(GameObject obj)
        {
            var missingNameIndex = obj.name.LastIndexOf(" (Missing Prefab)");
            var realName = missingNameIndex >= 0 ? obj.name.Remove(missingNameIndex) : obj.name;
            var findIndex = realName.LastIndexOf(" (");
            return findIndex < 0 ? realName : realName.Substring(0, findIndex);
        }

        bool IsValidPrefab(GameObject target)
        {
#if UNITY_2018_1_OR_NEWER
            var pType = PrefabUtility.GetPrefabAssetType(target);
            return pType != PrefabAssetType.NotAPrefab && pType != PrefabAssetType.MissingAsset;
#else
            var pType = PrefabUtility.GetPrefabType(target);
            return pType == PrefabType.PrefabInstance && pType != PrefabAssetType.MissingAsset;
#endif
        }
    }
}